/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.kramerius.articles.importFromLn.articleImport;

import cz.mzk.kramerius.articles.importFromLn.PropertyNames;
import cz.mzk.kramerius.articles.importFromLn.domain.Article;
import cz.mzk.kramerius.articles.importFromLn.domain.ArticleComponent;
import cz.mzk.kramerius.articles.importFromLn.domain.Author;
import cz.mzk.kramerius.articles.importFromLn.domain.Issue;
import cz.mzk.kramerius.articles.importFromLn.krameriusAccess.Repository;
import cz.mzk.kramerius.commons.Dom4jUtils;
import cz.mzk.kramerius.commons.PropertyLoader;
import cz.mzk.kramerius.commons.RandomBasedUuidGenerator;
import cz.mzk.kramerius.commons.StringUtils;
import cz.mzk.kramerius.commons.UuidGenerator;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

/**
 *
 * @author Martin Řehánek (rehan@mzk.cz)
 */
public class Importer {

    private static final Logger logger = Logger.getLogger(Importer.class.getName());
    private static final boolean ADD_VOLUME_ARTICLE_MAPPING = false;
    private static final int IMPORT_DESCRIPTION_MAX_LENGTH = 20;
    //TODO: vsude prejmenovat number na issue
    private final Repository repository;
    private final UuidGenerator uuidGenerator;
    private final File foxmlDir;
    private final File importLogDir;
    private final Periodical periodical;
    private final String krameriusUrl;

    /**
     * Factory method for creating instances of Importer
     *
     * @param properties
     */
    public static Importer instanceOf(PropertyLoader properties) throws Exception {
        UuidGenerator uuidGenerator = RandomBasedUuidGenerator.instanceOf();
        //EthAddrUuidGenerator.instanceOf(properties);
        if (uuidGenerator == null) {
            throw new Exception("Could not initiate  " + uuidGenerator.getClass().getName());
        }
        Repository repository = Repository.instanceOf(properties);
        if (repository == null) {
            throw new Exception("Could not initiate  " + repository.getClass().getName());
        }
        File foxmlDir = properties.loadDir(PropertyNames.IMPORT_FOXML_DIR, true, false, true);
        File importLogDir = properties.loadDir(PropertyNames.IMPORT_LOG_DIR, true, false, true);
        String periodicalName = properties.loadString(PropertyNames.PERIODICAL_NAME);
        String periodicalIssn = properties.loadString(PropertyNames.PERIODICAL_ISSN);
        String periodicalPid = properties.loadString(PropertyNames.PERIODICAL_PID);
        Periodical periodical = new Periodical(periodicalName, periodicalIssn, periodicalPid);
        String krameriusUrl = properties.loadString(PropertyNames.KRAMERIUS_URL);
        return new Importer(periodical, uuidGenerator, repository, foxmlDir, importLogDir, krameriusUrl);
    }

    private Importer(Periodical periodical, UuidGenerator uuidGenerator, Repository repository, File foxmlDir, File importLogDir, String krameriusUrl) {
        this.periodical = periodical;
        this.repository = repository;
        this.uuidGenerator = uuidGenerator;
        this.foxmlDir = foxmlDir;
        this.importLogDir = importLogDir;
        this.krameriusUrl = krameriusUrl;
    }

    private ArticleImport createArticleImport(Article article) {
        Set<String> pagePids = new HashSet<String>();
        Set<String> volumePids = new HashSet<String>();
        Set<String> issuePids = new HashSet<String>();
        for (ArticleComponent part : article.getArticleComponents()) {
            Logger.getLogger(Importer.class.getName()).log(Level.INFO, "processing ArticleComponent");
            int volumeNumber = part.getVolume();
            //zjistit uuid daneho volumeNumber (asi periodical item)
            String volumePid = repository.getVolumePid(Integer.valueOf(volumeNumber));
            if (volumePid == null) {
                logger.log(Level.SEVERE, "No pid for volume with number {0} found. Skipping articleComponent", volumeNumber);
                continue;
            } else {
                volumePids.add(volumePid);
                logger.log(Level.INFO, "volume PID: ''{0}''", volumePid);
                Issue issue = part.getIssue();
                String issuePid = repository.getIssuePid(volumePid, Integer.valueOf(volumeNumber), issue);
                //TODO: nemuze byt issuePid null?
                issuePids.add(issuePid);
                logger.log(Level.INFO, "issue PID: ''{0}''", issuePid);
                //add items to the pagePids
                for (Integer pageNumber : part.getPageInterval().toNumberList()) {
                    //System.err.println("page number: " + pageNumber);
                    String pagePid = repository.getPagePid(issuePid, pageNumber);
                    pagePids.add(pagePid);
                }
            }
        }
        //String articlePid = pidGenerator.generatePid();
        String articleUuid = uuidGenerator.generateUuid();
        ArticleXml articleXml = new ArticleXml(periodical, article, articleUuid, pagePids, krameriusUrl);
        logger.log(Level.INFO, "Created articleXml with uuid ''{0}''", articleUuid);
        return new ArticleImport(articleUuid, articleXml, generateArticleDescription(article), volumePids, issuePids, pagePids);
    }

    private String generateArticleDescription(Article article) {
        String description = null;
        Author author = article.getAuthor();
        if (author != null) {
            description = article.getAuthor().getUnresolvedName() + ":";
        }
        description += article.getTitle();
        String withoutNotUnicodeCharacters = StringUtils.removeUndefinedUnicodeCharacters(description);
        String trimmed = StringUtils.trimString(withoutNotUnicodeCharacters, IMPORT_DESCRIPTION_MAX_LENGTH, true);
        return trimmed;
    }

    /**
     * Imports article into repository
     *
     * @param article
     * @param saveFoxml if true the article copy will be saved to file
     * $ARTICLE_PID.foxml (directory is defined in properties)
     * @param saveImportLog if true the xml with pid of imported article and
     * pids of updated issues is saved into the file $ARTICLE_PID.xml (directory
     * is defined in properties) This file can be later used to rollback the
     * import
     * @return PID of imported article or null if import failed
     */
    public String importArticle(Article article, boolean saveFoxml, boolean saveImportLog) {
        ArticleImport articleImport = createArticleImport(article);
        if (articleImport == null) {
            logger.log(Level.SEVERE, "ArticleImport instance has not been created");
            return null;
        } else {
            String articlePid = articleImport.getArticlePid();
            Document articleDoc = importArticle(articleImport, ADD_VOLUME_ARTICLE_MAPPING);
            if (articleDoc == null) {
                logger.log(Level.SEVERE, "Could not import article with PID ''{0}''", articlePid);
                return null;
            } else {
                saveFedoraImportLog(articleImport, ADD_VOLUME_ARTICLE_MAPPING);
                saveArticleXml(articlePid, articleDoc);
                return articlePid;
            }
        }
    }

    /**
     * Just for testing purposes. Only saves generated foxmls and logs without
     * importing anything into repository
     *
     * @param article
     * @return
     */
    public String saveArticleFoxmlAndLog(Article article) {
        ArticleImport articleImport = createArticleImport(article);
        if (articleImport == null) {
            logger.log(Level.SEVERE, "ArticleImport instance has not been created");
            return null;
        } else {
            String articlePid = articleImport.getArticlePid();
            Document articleDoc = articleImport.getArticleXml().asDocument();
            if (articleDoc == null) {
                logger.log(Level.SEVERE, "Could not import article with PID ''{0}''", articlePid);
                return null;
            } else {
                saveFedoraImportLog(articleImport, ADD_VOLUME_ARTICLE_MAPPING);
                saveArticleXml(articlePid, articleDoc);
                return articlePid;
            }
        }
    }

    /**
     * Imports article and all necessary relationships into the Fedora
     * repository
     *
     * @param articleImport
     * @param addVolumeArticleRelationship if true not only issue-article but
     * also volume-article relationship is added
     * @return Document with generated foxml or null if failed to import article
     * //TODO: prece mozna presunout jinam
     */
    public Document importArticle(ArticleImport articleImport, boolean addVolumeArticleRelationship) {
        Document articleDoc = articleImport.getArticleXml().asDocument();
        String importLogMessage = "importing article " + articleImport.getDescription();
        String removeLogMessage = "removing article " + articleImport.getDescription();
        //import article foxml
        String returnedPid = repository.importFoxml(articleDoc, importLogMessage);
        String articlePid = articleImport.getArticlePid();
        if (!articlePid.equals(returnedPid)) {
            Logger.getLogger(Repository.class.getName()).log(Level.SEVERE, "Returned PID ''{0}'' differs from generated PID ''{1}''", new Object[]{returnedPid, articlePid});
            repository.removeArticle(returnedPid, removeLogMessage);
            return null;
        } else {
            Logger.getLogger(Repository.class.getName()).log(Level.FINE, "Returned PID ''{0}'' equals generated PID ''{1}''", new Object[]{returnedPid, articlePid});
            //add volume-article relationships
            Set<String> volumePids = articleImport.getVolumePids();
            if (addVolumeArticleRelationship) {
                if (repository.addVolumeArticleRelationships(articlePid, volumePids)) {
                    Logger.getLogger(Repository.class.getName()).log(Level.INFO, "Added volume-article relationships for all {0} volumes of article ''{1}''", new Object[]{volumePids.size(), articlePid});
                } else {
                    Logger.getLogger(Repository.class.getName()).log(Level.INFO, "Failed to add some volume-article relationships of article ''{0}''", articlePid);
                    repository.removeVolumeArticleRelationships(articlePid, volumePids);
                    repository.removeArticle(articlePid, removeLogMessage);
                    return null;
                }
            }
            //add issue-article realtionships
            Set<String> issuePids = articleImport.getIssuePids();
            if (repository.addIssueArticleRelationships(articlePid, issuePids)) {
                Logger.getLogger(Repository.class.getName()).log(Level.INFO, "Added issue-article relationships for all {0} issues of article ''{1}''", new Object[]{issuePids.size(), articlePid});
            } else {
                Logger.getLogger(Repository.class.getName()).log(Level.INFO, "Failed to add some issue-article relationships of article ''{0}''", articlePid);
                if (addVolumeArticleRelationship) {
                    repository.removeArticle(articlePid, volumePids, issuePids);
                } else {
                    repository.removeArticle(articlePid, issuePids);
                }
                return null;
            }
            return articleDoc;
        }
    }

    private void saveFedoraImportLog(ArticleImport articleImport, boolean saveVolumeArticleMapping) {
        File importLogFile = createImportLogFile(articleImport.getArticlePid());
        Document importLogDoc;
        if (saveVolumeArticleMapping) {
            importLogDoc = createImportLogDocument(articleImport.getArticlePid(), articleImport.getVolumePids(), articleImport.getIssuePids());
        } else {
            importLogDoc = createImportLogDocument(articleImport.getArticlePid(), articleImport.getIssuePids());
        }
        if (importLogDir.canWrite()) {
            Dom4jUtils.saveDocument(importLogDoc, importLogFile);
            logger.log(Level.INFO, "Fedora import log saved into file {0}", importLogFile.getAbsolutePath());
        } else {
            logger.log(Level.SEVERE, "Cannot create file {0}", importLogFile.getAbsolutePath());
        }
    }

    private File createImportLogFile(String articlePid) {
        StringBuilder importLogFilename = new StringBuilder();
        importLogFilename.append(importLogDir);
        importLogFilename.append(File.separatorChar);
        importLogFilename.append(articlePid);
        importLogFilename.append(".xml");
        return new File(importLogFilename.toString());
    }

    private void saveArticleXml(String articlePid, Document articleDoc) {
        StringBuilder outFoxmlFilename = new StringBuilder(4);
        outFoxmlFilename.append(foxmlDir);
        outFoxmlFilename.append(File.separatorChar);
        outFoxmlFilename.append(articlePid);
        outFoxmlFilename.append(".foxml");
        File outFoxmlFile = new File(outFoxmlFilename.toString());
        if (foxmlDir.canWrite()) {
            Dom4jUtils.saveDocument(articleDoc, outFoxmlFile);
            logger.log(Level.INFO, "Article foxml saved into file {0}", outFoxmlFile.getAbsolutePath());
        } else {
            logger.log(Level.SEVERE, "Cannot create file {0}", outFoxmlFile.getAbsolutePath());
        }
    }

    private Document createImportLogDocument(String articlePid, Set<String> issuePids) {
        return createImportLogDocument(articlePid, null, issuePids);
    }

    private Document createImportLogDocument(String articlePid, Set<String> volumePids, Set<String> issuePids) {
        //TODO: namespace, schema
        Document document = DocumentHelper.createDocument();
        Element articleEl = document.addElement("article");
        articleEl.addAttribute("PID", articlePid);
        if (volumePids != null) {
            for (String volumePid : volumePids) {
                Element volumeEl = articleEl.addElement("volume");
                volumeEl.addAttribute("PID", volumePid);
            }
        }
        for (String issuePid : issuePids) {
            Element issueEl = articleEl.addElement("issue");
            issueEl.addAttribute("PID", issuePid);
        }
        return document;
    }

    /**
     * Imports a test article (obtained by Article.getTestInstance())
     *
     * @return PID of imported article
     */
    public String importTestArticle() {
        Article article = Article.getTestInstance();
        return importArticle(article, true, true);
    }
}
